/* dongle.c
**
** Copyright 2006, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License"); 
** you may not use this file except in compliance with the License. 
** You may obtain a copy of the License at 
**
**     http://www.apache.org/licenses/LICENSE-2.0 
**
** Unless required by applicable law or agreed to in writing, software 
** distributed under the License is distributed on an "AS IS" BASIS, 
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
** See the License for the specific language governing permissions and 
** limitations under the License.
*/

/** returns 1 if line starts with prefix, 0 if it does not */

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "ppp_ctrl.h"
#include "modem_usb.h"
#include "atchannel.h"
#include <telephony/dongle.h>
#include "sunxi_ril.h"
#include "uevent.h"
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <errno.h>
#include "dongle_client.h"
#include "dongle_config.h"
#include "dongle_util.h"
#define LOG_TAG "DONGLE"
#include <utils/Log.h>
#include "cutils/properties.h"
#define DONGLE_LOG 1

struct _Dongle
{
	DongleState  		state;
	DongleConfig*		config;
	DongleClient*		client;
	PppCtrl* 			ppp;

	DongleFailCause		fail_cause;
	void*				user_data;
	unsigned int 		pid_vid_now;
	OnDongleDataStateChangedFunc on_state_changed;
	char* 				devices[MODEM_USBSERIAL_MAX_VALID_INDEX];
	int 				device_count;
};

#define THINKWILL_WCDMA_PID_VID 0x19f59013
#define THINKWILL_EVDO_PID_VID 	0x19f59909
#define SPREDTRUM_WCDMA_PID_VID 0x17820002
#define SPREDTRUM_WCDMA_UPDATE_PID_VID 0x17824D00

static Dongle* s_dongle = NULL;

static void dongle_set_fail_cause(Dongle* thiz, DongleFailCause cause);
static char* dongle_state_to_string(DongleState state);
static void usb_uevent_handler(void* data, const char* msg, int msg_len);
static unsigned int get_pid_vid_from_property();
static const char* check_at_port(Dongle* thiz, unsigned int pid_vid, int* zte_dm);

static void dongle_enter_dongle_state(Dongle* thiz, DongleState state)
{
#ifdef DONGLE_LOG
	ALOGD("%s: %p %s\n", __func__, thiz->on_state_changed, dongle_state_to_string(state));
#endif
	if(thiz->state != state)
	{
		if(thiz->state == DONGLE_STATE_USB_REMOVED
			&& state != DONGLE_STATE_USB_ADDED)
		{
			// ignore all state changing when usb removed
			return;
		}
		thiz->state = state;
		if(thiz->on_state_changed != NULL)
		{
			thiz->on_state_changed(thiz, state, thiz->user_data);
		}
		if(thiz->state == DONGLE_STATE_FAILED) 
		{
			//try next candidate config
			dc_turn_next_info(thiz->config);
			struct modem_info* modem = dc_get_current_modem_info(thiz->config);
			if(modem != NULL)
			{
				client_set_modem_info(thiz->client, modem);
				thiz->state == DONGLE_STATE_CONNECTING;
			}
		}
	}

	return;
}

static void handle_ppp_state_idle(Dongle* thiz)
{
#ifdef DONGLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	if(thiz->state == DONGLE_STATE_CONNECTING
            || thiz->state == DONGLE_STATE_CONNECTED)
	{
		client_stop_pppd(thiz->client);
		dongle_enter_dongle_state(thiz, DONGLE_STATE_FAILED);
	}
}

static void handle_ppp_state_up(Dongle* thiz)
{
#ifdef DONGLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	dongle_enter_dongle_state(thiz, DONGLE_STATE_CONNECTED);
	dongle_set_fail_cause(thiz, DONGLE_FAILED_CAUSE_SUCCESS);	
}

static void handle_ppp_state_down(Dongle* thiz)
{
#ifdef DONGLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	if(thiz->state == DONGLE_STATE_CONNECTED)
	{
		client_stop_pppd(thiz->client);
		dongle_enter_dongle_state(thiz, DONGLE_STATE_FAILED);
	}
}

char* dongle_state_to_string(DongleState state)
{
	switch(state) {
		case DONGLE_STATE_IDLE:
			return "IDLE";
		case DONGLE_STATE_USB_ADDED:
			return "USB_ADDED";
		case DONGLE_STATE_CONNECTING:
			return "CONNECTING";
		case DONGLE_STATE_CONNECTED:
			return "CONNECTED";
		case DONGLE_STATE_DISCONNECTING:
			return "DISCONNECTING";
		case DONGLE_STATE_DISCONNECTED:
			return "DISCONNECTED";
		case DONGLE_STATE_USB_REMOVED:
			return "USB_REMOVED";
		case DONGLE_STATE_FAILED:
			return "FAILED";
		default:
			return "UNKOWN";
	}
}

static void dongle_ppp_on_state_changed(PppCtrl* ppp, PppState state, void* data)
{
	Dongle* thiz = (Dongle*)data;

#ifdef DONGLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	switch(state)
	{
	case PPP_STATE_IDLE:
		handle_ppp_state_idle(thiz);
		break;

	case PPP_STATE_UP:
		handle_ppp_state_up(thiz);
		break;

	case PPP_STATE_DOWN:
		handle_ppp_state_down(thiz);
		break;
	
	default:
#ifdef DONGLE_LOG
		ALOGD("%s: error state", __func__);
#endif
		break;
	}

	return;
}

Dongle* dongle_create(OnDongleDataStateChangedFunc onStateChanged, void* user_data)
{
	Dongle* thiz = calloc(1, sizeof(Dongle));
	
#ifdef DONGLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	thiz->on_state_changed = onStateChanged;
	thiz->user_data = user_data;
	thiz->ppp = ppp_ctrl_create_with_thread(dongle_ppp_on_state_changed, thiz);
	uevent_add_native_handler(usb_uevent_handler, (void *)thiz);
	
	return thiz;
}

char* dongle_get_ppp_interface(Dongle* thiz)
{
#ifdef DONGLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	if(thiz != NULL)
	{
		return client_get_ppp_interface(thiz->client);
	}

	return NULL;
}

#define EMXX_OHCI_REMOVE_EVENT_PREFIX 	"remove@/devices/platform/emxx-ohci-driver/usb"
#define EMXX_OHCI_ADD_EVENT_PREFIX		"add@/devices/platform/emxx-ohci-driver/usb"

#define EMXX_EHCI_REMOVE_EVENT_PREFIX 	"remove@/devices/platform/emxx-ehci-driver/usb"
#define EMXX_EHCI_ADD_EVENT_PREFIX		"add@/devices/platform/emxx-ehci-driver/usb"

#define A10_REMOVE_EVENT_PREFIX         "remove@/devices/platform/sw_hcd_host0/usb"
#define A10_ADD_EVENT_PREFIX            "add@/devices/platform/sw_hcd_host0/usb"

#define SW_EHCI_REMOVE_EVENT_PREFIX     "remove@/devices/platform/sw-ehci.1/usb"
#define SW_EHCI_ADD_EVENT_PREFIX        "add@/devices/platform/sw-ehci.1/usb"

#define SW_HCD_REMOVE_EVENT_PREFIX      "remove@/devices/platform/sunxi_hcd_host0/usb"
#define SW_HCD_ADD_EVENT_PREFIX 		"add@/devices/platform/sunxi_hcd_host0/usb"

static int tty_device_matched(const char* buffer)
{
	return strstr(buffer, "ttyUSB") != NULL 
		|| strstr(buffer, "ttyACM") != NULL;
}

static int is_add_event(const char* buffer)
{
	//the complete buffer is like add@/devices/platform/emxx-ohci-driver/usb1/1-1/1-1.2
	//or add@/devices/platform/emxx-ohci-driver/usb1/1-1
	return (strStartsWith(buffer, EMXX_OHCI_ADD_EVENT_PREFIX)  
				|| strStartsWith(buffer, SW_EHCI_ADD_EVENT_PREFIX)
				|| strStartsWith(buffer, EMXX_EHCI_ADD_EVENT_PREFIX)
				|| strStartsWith(buffer, A10_ADD_EVENT_PREFIX)
				|| strStartsWith(buffer, SW_HCD_ADD_EVENT_PREFIX)) 
			&& tty_device_matched(buffer);
}

static int is_remove_event(const char* buffer)
{
	return (strStartsWith(buffer, EMXX_OHCI_REMOVE_EVENT_PREFIX)  
				|| strStartsWith(buffer, SW_EHCI_REMOVE_EVENT_PREFIX)
				|| strStartsWith(buffer, EMXX_EHCI_REMOVE_EVENT_PREFIX)
				|| strStartsWith(buffer, A10_REMOVE_EVENT_PREFIX)
				|| strStartsWith(buffer, SW_HCD_REMOVE_EVENT_PREFIX))
			&& tty_device_matched(buffer);
}

char* dongle_get_port_path(Dongle* thiz, int index)
{
	if(thiz != NULL) 
	{
		int i = 0;
		int has_acm = 0;
		int has_usb = 0;
		for(; i < thiz->device_count; i++)
		{
			if(!has_acm && strstr(thiz->devices[i], "ttyACM") != NULL)
			{
				has_acm = 1;
			} 
			else if(!has_usb && strstr(thiz->devices[i], "ttyUSB") != NULL)
			{
				has_usb = 1;
			}
		}
		if(has_acm && has_usb && index >= 0 && index < thiz->device_count)
		{
			i = 0;
			for(; i < thiz->device_count; i++)
			{
				//ACM device has higher priority
				if(strstr(thiz->devices[i], "ttyACM") != NULL)
				{
					if(index == 0)
					{
						return thiz->devices[i];
					}
					index--;
				}
			}
		}
		else if(index >= 0 && index < thiz->device_count)
		{
			return thiz->devices[index];
		}
	}
	return NULL;
}

static void dongle_create_config(Dongle* thiz, const char* name, unsigned int pid_vid) 
{
	int i = 0;

#ifdef DONGLE_LOG
	ALOGD("%s %d\n", __func__, __LINE__);
#endif
	thiz->pid_vid_now = pid_vid;
	thiz->config = dc_create(name, pid_vid);
	if(dc_get_config_length(thiz->config) == 0 && name != NULL) 
	{
		dc_destroy(thiz->config);
		thiz->config = dc_create(NULL, pid_vid);
	}
	dc_dump(thiz->config);
}

static void on_usb_add(Dongle* thiz, int* zte_dm, unsigned int pid_vid)
{
	initial_sleep_by_pid_vid(pid_vid);
	const char* name = check_at_port(thiz, pid_vid, zte_dm);
	dongle_create_config(thiz, name, pid_vid);
}

static void on_usb_removed(Dongle* thiz) 
{	
	thiz->pid_vid_now = 0;
	dc_destroy(thiz->config);
	thiz->config = NULL;
	client_destory(thiz->client);
	thiz->client = NULL;
	int i = 0;
	for(; i < thiz->device_count; i++)
	{
		if(thiz->devices[i] != NULL)
		{
			free(thiz->devices[i]);
			thiz->devices[i] = NULL;
		}
	}
	thiz->device_count = 0;
}
	
static const char* check_at_port(Dongle* thiz, unsigned int pid_vid, int* zte_dm)
{	
	ATResponse *p_response = NULL;
	ATChannel* channel = NULL;
	char* response = NULL;
	int i = 0;
	int fd = 0;
	int ret = 0;
	char* device_path = NULL;
	int has_acm = 0;
	
	for(; i < thiz->device_count; i++)
	{
		if(strstr(thiz->devices[i], "ttyACM") != NULL)
		{
			has_acm = 1;
			break;
		}
	}
	i = 0;
	for(; i < thiz->device_count; i++)
	{
		device_path = thiz->devices[i];
		if(has_acm && strstr(device_path, "ttyACM") == NULL)
		{
			continue;
		}
#ifdef DONGLE_LOG
		ALOGD("%s: Try open %s", __func__, device_path);
#endif
		if((fd = open (device_path, O_RDWR)) >= 0)
		{
			/* disable echo on serial ports */
			struct termios  ios;
			tcgetattr( fd, &ios );
			ios.c_lflag = 0;  /* disable ECHO, ICANON, etc... */
			tcsetattr( fd, TCSANOW, &ios );
		}
		else 
		{
#ifdef DONGLE_LOG
			ALOGD("Open %s failed, try others...", device_path);
#endif
			continue;
		}
#ifdef DONGLE_LOG
        ALOGD("Open %s succeed", device_path);
#endif
		
		int times = 2;
		char *buf[13] = {0};
		do
		{
			//Directly send at command
			if(check_is_dm_dongle_by_path(pid_vid, device_path)) 
			{
#ifdef DONGLE_LOG
				ALOGD("Write hex---------");
#endif
				char data[5]={0x01,0xF1,0xE1,0x7E,'\0'};
				ret = write_line(fd, data);
				*zte_dm = 1;
			} 
			else
			{
				ret = write_line(fd, "ATE0");
				*zte_dm = 0;
			}
#ifdef DONGLE_LOG
			ALOGD("write_line ret = %d", ret);
#endif

			//receive	response
			ret = read_timeout(fd, buf, 12, 30000);//30ms, what about the time?
#ifdef DONGLE_LOG
			ALOGD("read_timeout ret = %d", ret);	
#endif
			if((ret == -1) || (ret == ETIME))
        		continue; // with no response
        	else break;	  // with response, got it!        	
		}while( --times > 0 );		
		
		if(ret == -1 || ret == ETIME)	
		{
			close(fd);
			fd = -1;
#ifdef DONGLE_LOG
			ALOGD("AT port<%s> with no response,try others", device_path);
#endif
        	continue;
        }
        
        break;
	}

	if(fd > 0) 
	{
		close(fd);
		fd = -1;
	}

	if(ret == -1 || ret == ETIME || i == thiz->device_count) 
	{
		//if dm dongle is not aswered, abort it;
		*zte_dm = 0;
#ifdef DONGLE_LOG
		ALOGD("Find none suitable AT port");	
#endif
		return NULL;
	}
#ifdef DONGLE_LOG
		ALOGD("%s:%d zte_dm:%d, device:%s", __func__, __LINE__, *zte_dm, device_path);
#endif

	if(*zte_dm == 0) 
	{
		channel = at_channel_create(PORT_TYPE_SERIAL, device_path, NULL, NULL);
		if(at_command_exec_singleline_for_result(channel, "AT+CGMM", 5000, "", &p_response))
		{
			if(p_response->p_intermediates->line != NULL)
			{
				response = strdup(p_response->p_intermediates->line);
			}
		}
		at_response_free(p_response);
		at_channel_destroy(channel);
	}

	return response;
}

static int pid_vid_from_inner_modem(unsigned int pid_vid)
{
	if(pid_vid == THINKWILL_WCDMA_PID_VID 
		|| pid_vid == THINKWILL_EVDO_PID_VID
		|| pid_vid == SPREDTRUM_WCDMA_PID_VID
		|| pid_vid == SPREDTRUM_WCDMA_UPDATE_PID_VID)
	{
		return 1;
	}

	return 0;
}

int inner_modem_usb_index()
{
	char modem[PROPERTY_VALUE_MAX] = {0}; 
	property_get("ro.baseband", modem, "none");
	if(strStartsWith(modem, "m7281"))
	{
		return 2;
	}
	if(strStartsWith(modem, "tw"))
	{
		return 3;
	}

	return -1;
}

static void usb_uevent_handler(void* data, const char* msg, int msg_len)
{
	static int usb_added = 0;
	Dongle* thiz = (Dongle *)data;
	
	if(is_add_event(msg))
	{
		unsigned int pid_vid = get_pid_vid_from_property();

		if(pid_vid_from_inner_modem(pid_vid))
		{
			return;
		}
		char* ptr = strstr(msg, "ttyUSB");
		if(ptr == NULL)
		{
			ptr = strstr(msg, "ttyACM");
		}
		
		if(ptr == NULL || strlen(ptr) > sizeof("ttyUSBxx"))
		{
			return;
		}

		int index = 0;
		sscanf(ptr + strlen("ttyUSB"), "%d", &index);
		if(strStartsWith(ptr, "ttyUSB") && index <= inner_modem_usb_index())
		{
			return;
		}

		thiz->pid_vid_now = pid_vid;
		int count = thiz->device_count;
		thiz->devices[count] = calloc(1, 16);
		snprintf(thiz->devices[count], 16, "/dev/%s", ptr);
#ifdef DONGLE_LOG
		ALOGD("%s %d %s added", __func__, __LINE__, thiz->devices[count]);
#endif
		thiz->device_count++;
		usb_added = 1;
		dongle_enter_dongle_state(thiz, DONGLE_STATE_USB_ADDED);
	}
	else if(is_remove_event(msg) && usb_added)
	{
		char* ptr = strstr(msg, "ttyUSB");
		if(ptr == NULL)
		{
			ptr = strstr(msg, "ttyACM");
		}
		if(ptr == NULL)
		{
			return;
		}
		char port[16] = {0};
		snprintf(port, 16, "/dev/%s", ptr);

		int i = 0;
		for(; i < thiz->device_count; i++)
		{
			if(!strncmp(port, thiz->devices[i], 16))
			{
				break;
			}
		}
		if(i == thiz->device_count)
		{
			return;
		}
		usb_added = 0;
		on_usb_removed(thiz);
		dongle_enter_dongle_state(thiz, DONGLE_STATE_USB_REMOVED);
	} 
}

char* dongle_get_operator_numeric(Dongle* thiz) 
{
	if(thiz->device_count == 0 || thiz->client == NULL)
	{
#ifdef DONGLE_LOG
		ALOGD("%s: not ready to get numeric, device do not exist, or client hasn't created", __func__);
#endif
		return NULL;
	}
	//FIXME maybe we can put cond lock here
	char* ret = client_get_operator_numeric(thiz->client);
	if(ret == NULL) 
	{
#ifdef DONGLE_LOG
		ALOGD("%s client_get_operator_numeric failed, turn next config", __func__);
#endif
		dc_turn_next_info(thiz->config);
		struct modem_info* modem = dc_get_current_modem_info(thiz->config);
		if(modem != NULL)
		{
			client_set_modem_info(thiz->client, modem);
		}
	}

	return ret;
}

Dongle*	dongle_get_instance(OnDongleDataStateChangedFunc onStateChanged, void* user_data)
{
#ifdef DONGLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	if(s_dongle == NULL)
	{
		s_dongle = dongle_create(onStateChanged, user_data);
		dongle_set_fail_cause(s_dongle, DONGLE_FAILED_CAUSE_NONE);
		modem_usb_init();
	}

	return s_dongle;
}

void dongle_destroy(Dongle* thiz)
{
#ifdef DONGLE_LOG
	ALOGD("%s: %p \n", __func__, thiz);
#endif
	if(thiz != NULL)
	{
		client_destory(thiz->client);
		thiz->client = NULL;
		modem_usb_uninit();
		ppp_ctrl_destroy(thiz->ppp);
		thiz->ppp = NULL;

		free(thiz);
		thiz = NULL;
	}
}

char* dongle_get_name(Dongle* thiz) 
{
#ifdef DONGLE_LOG
	ALOGD("%s %d %p", __func__, __LINE__, thiz->client);
#endif

	if(thiz != NULL && thiz->client != NULL) 
	{
		return client_get_dongle_name(thiz->client);
	}

	return NULL;
}

DongleFailCause dongle_get_fail_cause(Dongle* thiz)
{
#ifdef DONGLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	if(thiz != NULL)
	{
		return thiz->fail_cause;
	}
	
	return DONGLE_FAILED_CAUSE_NONE;
}

DongleState 	dongle_get_state(Dongle* thiz)
{
#ifdef DONGLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	if(thiz != NULL)
	{
		if(thiz->client == NULL && thiz->device_count > 0 && thiz->state != DONGLE_STATE_USB_REMOVED) 
		{
			sleep(2);
#ifdef DONGLE_LOG
			ALOGD("%s %d %d", __func__, __LINE__, thiz->device_count);
#endif
			int i = 0;
			for(; i < thiz->device_count; i++)
			{
#ifdef DONGLE_LOG
				ALOGD("%s %s\n", __func__, thiz->devices[i]);
#endif
			}
			int zte_dm = 0;
			on_usb_add(thiz, &zte_dm, thiz->pid_vid_now);	
			if(zte_dm)
			{
				dc_filt_dm_config(thiz->config);
			}
			if(dc_get_config_length(thiz->config) > 0) 
			{
				thiz->client = dongle_client_create(thiz, thiz->ppp, dc_get_current_modem_info(thiz->config));		
			}
		}

		return thiz->state;
	}

	return DONGLE_STATE_IDLE;
}

static unsigned int get_pid_vid_from_property() {
	char svid[PROPERTY_VALUE_MAX] = {0};
	char spid[PROPERTY_VALUE_MAX] = {0};

	property_get("system.modem.vid", svid, "");
	property_get("system.modem.pid", spid, "");
	//TODO May introduce bug here
	unsigned int vid = atoi(svid);
	unsigned int pid = atoi(spid);
	
	return (vid << 16) | pid;
}

static void dongle_set_fail_cause(Dongle* thiz, DongleFailCause cause) 
{
	thiz->fail_cause = cause;
}

static int dongle_is_not_ready(Dongle* thiz) 
{
	int ready = 1;
	switch(thiz->state){
		case DONGLE_STATE_CONNECTING:
			ready = 0;
			dongle_set_fail_cause(thiz, DONGLE_FAILED_CAUSE_CONNECTING);
			break;
		case DONGLE_STATE_CONNECTED:
			ready = 0;
			dongle_set_fail_cause(thiz, DONGLE_FAILED_CAUSE_ALREADY_CONNECTED);
			break;
		case DONGLE_STATE_DISCONNECTING:
			ready = 0;
			dongle_set_fail_cause(thiz, DONGLE_STATE_DISCONNECTING);
			break;
		case DONGLE_STATE_USB_REMOVED:
			ready = 0;
			dongle_set_fail_cause(thiz, DONGLE_FAILED_CAUSE_USB_REMOVED);
			break;
		default:
			break;
	}

	return ready;
}

int dongle_setup_data_connection(Dongle* thiz, const char* apn, const char* user, const char* pwd)
{
#ifdef DONGLE_LOG
	ALOGD("%s: thiz=%p\n", __func__, thiz);
#endif
	if(thiz == NULL || !dongle_is_not_ready(thiz)) 
	{
#ifdef DONGLE_LOG
		ALOGD("Can not setup data call, state:%s\n", dongle_state_to_string(thiz->state));
#endif

		return 0;
	}

	dongle_enter_dongle_state(thiz, DONGLE_STATE_CONNECTING);
	if(client_setup_data_connection(thiz->client, apn, user, pwd)) 
	{
		dongle_enter_dongle_state(thiz, DONGLE_STATE_FAILED);	
	}

	return 0;
}

int	dongle_disconnect_data_connection(Dongle* thiz)
{
#ifdef DONGLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	if(thiz != NULL)
	{
		client_stop_pppd(thiz->client);
		dongle_enter_dongle_state(thiz, DONGLE_STATE_DISCONNECTING);
		sleep(2);
		
		return 1;
	}

	return 0;
}

static void dongle_test_on_state_changed(Dongle* thiz, DongleState state, void* user_data)
{
#ifdef DONGLE_LOG
	ALOGD("%s: state=%d, user_data=%p", __func__, state, user_data);
#endif
}

int main (int argc, char** argv)
{
	Dongle*  dongle = dongle_get_instance(dongle_test_on_state_changed, NULL);

#ifdef DONGLE_LOG
	ALOGD("%s: \n", __func__);
#endif
	
	while(1)
	{
		int state = dongle_get_state(dongle);
		if(state == DONGLE_STATE_USB_ADDED)
		{
			dongle_setup_data_connection(dongle, "cmnet", "", "");
		}
		else if(state == DONGLE_STATE_USB_REMOVED)
		{
			dongle_disconnect_data_connection(dongle);			
		}
		sleep(2);
	}

	sleep(10000);
	dongle_disconnect_data_connection(dongle);
	dongle_destroy(dongle);

	return 1;
}

